/*
 * Copyright (c) 2018-2024 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "subcmd.h"

static int debugReqCode[] = 
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    JS_DEBUG, JS_TIMING, 0, JM_DEBUG, JM_TIMING,  
    0, 0, 0, 0, 0, 0,
    0, 0
};

static const char *adminSyntax[] = 
{
    "qp js reconfig usage: [-h, --help] [-v, --verbose] [-f --force]",
    "qp js checkconfig usage: [-h, --help] [-v, --verbose]",
    "qp queue open usage: [-h, --help] [queue_name... | all]",
    "qp queue close usage: [-h, --help] [queue_name... | all]",
    "qp queue act usage: [-h, --help] [queue_name... | all]",
    "qp queue inact usage: [-h, --help] [queue_name... | all]",
    "qp queue hist usage: [-h, --help] [-t, --time time_begin, time_end]\n\t\t     [-f, --file logfile_name] [queue_name ...]",
    "qp node open usage: [-h, --help] [host_name... | host_group... | all]",
    "qp node close usage: [-h, --help] [host_name... | host_group... | all]",
    "qp jm restart usage: [-h, --help] [-f, --force] [host_name... | all]",
    "qp jm stop usage: [-h, --help] [-f, --force] [host_name... | all]",
    "qp node hist usage: [-h, --help] [-t, --time time_begin, time_end]\n\t\t    [-f, --file logfile_name] [host_name...]",
    "qp js hist usage: [-h, --help] [-t, --time time_begin, time_end]\n\t\t  [-f, --file logfile_name]",
    "qp js debug usage: [-h, --help] [-c, --class class_name]\n\t\t   [-l, --level debug_level] [-f, --file logfile_name] [-o, --off]",
    "qp js time usage: [-h, --help] [-l, --level timing_level]\n\t\t  [-f, --file logfile_name] [-o, --off]",
    "qp js restart usage: [-h, --help] [-v, --verbose] [-f, --force]",
    "qp jm debug usage: [-h, --help] [-c, --class class_name]\n\t\t   [-l, --level debug_level] [-f, --file logfile_name]\n\t\t   [-o, --off] host_name ... | all",
    "qp jm time usage: [-h, --help] [-l, --level timing_level]\n\t\t  [-f, --file logfile_name] [-o, --off] host_name... | all",
    "qp queue nodeadd usage: [-h, --help] -m, --node [host_name... | all] queue_name",
    "qp queue noderemove usage: [-h, --help] -m, --node [host_name... | all] queue_name",
    "qp queue nodeupdate usage: [-h, --help] -m, --node [host_name... | all] queue_name",
    "qp jm start usage: [-h, --help] [-f, --force] [host_name... | all]",
    "qp jm reconfig usage: [-h, --help] [-v, --verbose] [-f --force]",
    "qp jm checkconfig usage: [-h, --help] [-v, --verbose]",
    NULL,
};

static int check_js_config(int);
static int admin_debug(int, char *[], admin_control_t);
static int admin_reconfig (int, char **, admin_control_t);
static int check_jm_config(int);
static int admin_jm_reconfig (int, char **, admin_control_t);

/**
 * @brief       qp admin handler, to manage the quickpool cluster.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 * @param[in]   index         #3: index of the control, from 0-23
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to manage the cluster
 *
 */
int
cmd_admin_control(int argc, char *argv[], admin_control_t index)
{
    int cmdRet = 0;

    switch (index) {
    case ADMIN_JSRESTART:
    case ADMIN_RECONFIG:
    case ADMIN_CKCONFIG:
        cmdRet = admin_reconfig(argc, argv, index);
        break;
    case ADMIN_JM_RECONFIG:
    case ADMIN_JM_CKCONFIG:
        cmdRet = admin_jm_reconfig(argc, argv, index);
        break;
    case ADMIN_QUEUE_OPEN:
    case ADMIN_QUEUE_CLOSE:
    case ADMIN_QUEUE_ACT:
    case ADMIN_QUEUE_INACT:
    case ADMIN_QUEUE_NODEADD:
    case ADMIN_QUEUE_NODEREMOVE:
    case ADMIN_QUEUE_NODEUPDATE:
        cmdRet = cmd_manage_queue(argc, argv, index);
        break;
    case ADMIN_NODE_OPEN:
    case ADMIN_NODE_CLOSE:
    case ADMIN_JMRESTART:
    case ADMIN_NODE_SHUTDOWN:
         cmdRet = cmd_control_host(argc, argv, index);
        break;
    case ADMIN_QHIST:
    case ADMIN_HHIST:
    case ADMIN_JSHIST:
        cmdRet = admin_operate_hist(argc, argv, index);
        break;
    case ADMIN_JSDEBUG:
    case ADMIN_JSTIME:
    case ADMIN_JMDEBUG:
    case ADMIN_JMTIME:
        cmdRet = admin_debug(argc, argv, index);
        break;
    default :
        fprintf(stderr, "Unknown admin command index: %d\n", index);
        exit(1);
    }

    return (cmdRet);
} // end function cmd_admin_control

/**
 * @brief       To print out the admin control help message.
 *
 * @param[in]   index         #1: index of the control, from 0-23
 *
 */
void
admin_usage(int index)
{
    fprintf(stdout, "%s\n", adminSyntax[index]);
    fflush(stdout);

    return;
} // end function admin_usage

/**
 * @brief       qp jm reconfig/chkconfig handler,
 *              to restart or check configuration for jm.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 * @param[in]   index         #3: index of the control: RECONFIG or CKCONFIG
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to restart jm
 *
 */
static int
admin_jm_reconfig(int argc, char **argv, admin_control_t index)
{
    const char *options;
    static struct option long_options[4];
    long_options[0].name = "help";
    long_options[0].has_arg = no_argument;
    long_options[0].flag = 0;
    long_options[0].val = 'h';

    long_options[1].name = "verbose";
    long_options[1].has_arg = no_argument;
    long_options[1].flag = 0;
    long_options[1].val = 'v';

    switch (index) {
    case ADMIN_JM_CKCONFIG:
        options = "hv";

        long_options[2].name = NULL;
        long_options[2].has_arg = no_argument;
        long_options[2].flag = 0;
        long_options[2].val = 0;

        break;
    case ADMIN_JM_RECONFIG:
        options = "hvf";

        long_options[2].name = "force";
        long_options[2].has_arg = no_argument;
        long_options[2].flag = 0;
        long_options[2].val = 'f';

        long_options[3].name = NULL;
        long_options[3].has_arg = no_argument;
        long_options[3].flag = 0;
        long_options[3].val = 0;

        break;
    default:
        fprintf(stderr, "Unknown admin subcmd specified.\n");

        admin_usage(index);
        exit(1);
    }

    bool_t vOpt = FALSE;
    bool_t fOpt = FALSE;
    while (TRUE) {

        int option_index = 0;
        int opt;
        opt = getopt_long(argc, argv, options, long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'v':
            vOpt = TRUE;
            break;
        case 'f':
            fOpt = TRUE;
            break;
        case 'h':
            admin_usage(index);
            exit(0);
        case '?':
        default:
            admin_usage(index);
            exit(1);
        }
    }

    int ret;
    if (!fOpt) { // --force is not specified.

        if (vOpt) { // --verbose is specified.
            fprintf(stdout, "\nChecking configuration files for jm daemon...\n\n");
        }

        int stdoutsave;
        stdoutsave = dup(1);

        char filename[]="/tmp/quickpool_jmXXXXXX";
        int fd = mkstemp(filename); // warning messages will be written to this file.
        if (0 < fd) {

            dup2(fd, 1);
            dup2(fd, 2);
            ret = check_jm_config(vOpt);
            fflush(stderr);
            close(fd);
            dup2(stdoutsave, 1);
            dup2(stdoutsave, 2);
            FILE *fp;
            fp = fopen(filename, "r");
            if (NULL != fp) {
                if (-1 == ret || -2 == ret) {

                    if (-1 == ret) {
                        fprintf(stderr, "There are fatal errors.\n\n");
                    } else {
                        fprintf(stderr, "There are warning errors.\n\n");
                    }

                    fflush(stderr);

                    unsigned int lineNum = 0;
                    if (askfor_confirm((char *)"Do you want to see detailed messages? [y/n] ")) {
                        char buf[BUFSIZ];
                        char *strLine;

                        while (NULL != (strLine=gf_get_line(fp, &lineNum, buf))) {
                            fprintf(stderr, "%s\n", strLine);
                        }
                    }
                } else {
                    fprintf(stderr, "No errors found.\n\n");
                }

                fflush(stderr);
            }

            fclose(fp);
	        unlink(filename);
        } else {
            ret = check_jm_config(vOpt);
        }
    } else {
        // --force, ignore the warning from jm.
        ret = check_jm_config(vOpt);
    }

    if (index == ADMIN_JM_CKCONFIG ) {
        // We are done for checking configuration.
        exit(0);
    }

    switch (ret) {
    case -1: // fatal error in configure file.

        exit(2);
    case -2:

        if (fOpt) {
            // --force, ignore the warning message, do reconfig directly.
            break;
        }

        if (index == ADMIN_JM_RECONFIG) {

            if (!askfor_confirm((char *)"\nDo you want to reconfigure jm? [y/n] ")) {
                if (vOpt) {
                    fprintf(stderr, "Reconfiguration aborted.\n");
                }
                exit(3);
            }
        } else {

            if (!askfor_confirm((char *)"\nDo you want to restart jm? [y/n] ")) {
                if (vOpt) {
                    fprintf(stderr, "jm restart aborted.\n");
                }
                exit(3);
            }
        }

        break;
    default:

        break;
    }

    if (index == ADMIN_JM_RECONFIG) {
        /*send reconfig message to jm*/
        ret = cmd_control_host(argc, argv, ADMIN_JM_RECONFIG);
        if (ret){
            fprintf(stderr, "reconfig JM error.\n");
            exit(ret);
        }
    }

    exit(0);
} // end function ADMIN_JM_RECONFIG


/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

/**
 * @brief       qp js restart/reconfig/chkconfig handler,
 *              to restart or check configuration for JS.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 * @param[in]   index         #3: index of the control: JSRESTART, RECONFIG or CKCONFIG
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to restart JS
 *
 */
static int
admin_reconfig(int argc, char **argv, admin_control_t index)
{
    const char *options;
    static struct option long_options[4];
    long_options[0].name = "help";
    long_options[0].has_arg = no_argument;
    long_options[0].flag = 0;
    long_options[0].val = 'h';

    long_options[1].name = "verbose";
    long_options[1].has_arg = no_argument;
    long_options[1].flag = 0;
    long_options[1].val = 'v';

    switch (index) {
    case ADMIN_CKCONFIG:
        options = "hv";

        long_options[2].name = NULL;
        long_options[2].has_arg = no_argument;
        long_options[2].flag = 0;
        long_options[2].val = 0;

        break;
    case ADMIN_RECONFIG:
    case ADMIN_JSRESTART:
        options = "hvf";

        long_options[2].name = "force";
        long_options[2].has_arg = no_argument;
        long_options[2].flag = 0;
        long_options[2].val = 'f';

        long_options[3].name = NULL;
        long_options[3].has_arg = no_argument;
        long_options[3].flag = 0;
        long_options[3].val = 0;

        break;
    default:
        fprintf(stderr, "Unknown admin subcmd specified.\n");

        admin_usage(index);
        exit(1);
    }

    bool_t vOpt = FALSE;
    bool_t fOpt = FALSE;
    while (TRUE) {

        int option_index = 0;
        int opt;
        opt = getopt_long(argc, argv, options, long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'v':
            vOpt = TRUE;
            break;
        case 'f':
            fOpt = TRUE;
            break;
        case 'h':
            admin_usage(index);
            exit(0);
        case '?':
        default:
            admin_usage(index);
            exit(1);
        }
    }

    int ret;
    if (!fOpt) { // --force is not specified.

        if (vOpt) { // --verbose is specified.
            fprintf(stdout, "\nChecking configuration files for JS daemon...\n\n");
        }

        int stdoutsave;
        stdoutsave = dup(1);

        char filename[]="/tmp/quickpoolXXXXXX";
        int fd = mkstemp(filename); // warning messages will be written to this file.
        if (0 < fd) {

            dup2(fd, 1);
            dup2(fd, 2);

            ret = check_js_config(vOpt);
            fflush(stderr);
            close(fd);
            dup2(stdoutsave, 1);
            dup2(stdoutsave, 2);

            FILE *fp;
            fp = fopen(filename, "r");
            if (NULL != fp) {
                if (-1 == ret || -2 == ret) {

                    if (-1 == ret) {
                        fprintf(stderr, "There are fatal errors.\n\n");
                    } else {
                        fprintf(stderr, "There are warning errors.\n\n");
                    }

                    fflush(stderr);

                    unsigned int lineNum = 0;
                    if (askfor_confirm((char *)"Do you want to see detailed messages? [y/n] ")) {
                        char buf[BUFSIZ];
                        char *strLine;

                        while (NULL != (strLine=gf_get_line(fp, &lineNum, buf))) {
                            fprintf(stderr, "%s\n", strLine);
                        }
                    }
                } else {
                    fprintf(stderr, "No errors found.\n\n");
                }

                fflush(stderr);
            }

            fclose(fp);
            unlink(filename);
        } else {
            ret = check_js_config(vOpt);
        }
    } else {
        // --force, ignore the warning from JS.
        ret = check_js_config(vOpt);
    }

    if (index == ADMIN_CKCONFIG ) {
        // We are done for checking configuration.
        exit(0);
    }

    switch (ret) {
    case -1: // fatal error in configure file.

        exit(2);
    case -2:

        if (fOpt) {
            // --force, ignore the warning message, do reconfig directly.
            break;
        }

        if (index == ADMIN_RECONFIG) {

            if (!askfor_confirm((char *)"\nDo you want to reconfigure JS? [y/n] ")) {
                if (vOpt) {
                    fprintf(stderr, "Reconfiguration aborted.\n");
                }
                exit(3);
            }
        } else {

            if (!askfor_confirm((char *)"\nDo you want to restart JS? [y/n] ")) {
                if (vOpt) {
                    fprintf(stderr, "JS restart aborted.\n");
                }
                exit(3);
            }
        }

        break;
    default:

        break;
    }

    struct request reconfigRequest;
    memset(&reconfigRequest, 0, sizeof(struct request));

    // call JS to do reconfig/restart.
    if (client_reconfig(&reconfigRequest, NULL) < 0) {
        gf_xdr_free(gf_xdr_request, &reconfigRequest);
        exit(3);
    }

    if (index == ADMIN_RECONFIG) {
        fprintf(stdout, "Reconfiguration initiated\n");
    } else {
        fprintf(stdout, "JS restart initiated\n");
    }
    gf_xdr_free(gf_xdr_request, &reconfigRequest);

    exit(0);
} // end function admin_reconfig

/**
 * @brief       qp jm/js debug handler, to let jm/js log kind of debug messages.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 * @param[in]   index         #3: index of the control: JSDEBUG, JSTIME, JMDEBUG or JMTIME
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to debug JS/JM
 *
 */
static int
admin_debug(int argc, char *argv[], admin_control_t index)
{
    const char *options;
    static struct option long_options[6];

    long_options[0].name = "help";
    long_options[0].has_arg = no_argument;
    long_options[0].flag = 0;
    long_options[0].val = 'h';

    long_options[1].name = "off";
    long_options[1].has_arg = no_argument;
    long_options[1].flag = 0;
    long_options[1].val = 'o';

    long_options[2].name = "level";
    long_options[2].has_arg = required_argument;
    long_options[2].flag = 0;
    long_options[2].val = 'l';

    long_options[3].name = "file";
    long_options[3].has_arg = required_argument;
    long_options[3].flag = 0;
    long_options[3].val = 'f';

    switch (index) {
    case ADMIN_JSDEBUG:
    case ADMIN_JMDEBUG:
        options = "hol:f:c:";

        long_options[4].name = "class";
        long_options[4].has_arg = required_argument;
        long_options[4].flag = 0;
        long_options[4].val = 'c';

        long_options[5].name = NULL;
        long_options[5].has_arg = no_argument;
        long_options[5].flag = 0;
        long_options[5].val = 0;

        break;
    case ADMIN_JSTIME:
    case ADMIN_JMTIME:
        options = "hol:f:";

        long_options[4].name = NULL;
        long_options[4].has_arg = no_argument;
        long_options[4].flag = 0;
        long_options[4].val = 0;

        break;
    default:
        fprintf(stderr, "Unknown debug subcmd specified.\n");

        admin_usage(index);
        exit(1);
    }

    int debugOptions=0, level=0, logclass=0;
    char strFileName[PATH_MAX+1];
    strFileName[0] = '\0';
    while (TRUE) {

        int option_index = 0;
        int opt;
        opt = getopt_long(argc, argv, options, long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'c':
            logclass = gf_set_logclass(optarg);
            if (0 == logclass) {
                fprintf(stderr, "Specify an unknown logclass <%s>. Valid logclass are TRACER, PERFM, CHANNEL, SCHEDULE, DEBUGXDR, DEBUGRES, EXECUTE, PENDINGREASON, FAIRSHARE, PREEMPTION, EVENTFILE, JOBGROUP\n", optarg);
                exit(2);
            }

            break;
        case 'f':
            if ('/' != optarg[0]) {
                char workingDir[PATH_MAX];

                if (NULL == gf_getwd(workingDir)) {
                    fprintf(stderr, "Specified a relative file name <%s> and failed to get the working directory.\n", optarg);
                    exit(2);
                }

                snprintf(strFileName, PATH_MAX , "%s/%s", workingDir, optarg);
            } else {
                snprintf(strFileName, PATH_MAX , "%s", optarg);
            }

            if ('/' == strFileName[strlen(strFileName)-1]) {
                fprintf(stderr, "Specified a dir <%s>, should specify a file name.\n",
                        strFileName);
                exit(2);
            }

            break;
        case 'l':
             if (!isdigit_string(optarg)) {
                 fprintf(stderr, "Specified wrong debug level <%s>, should be an integer.\n",
                         optarg);
                 exit(2);
             }

            if (index == ADMIN_JSDEBUG || index == ADMIN_JMDEBUG) {

               level = gf_atoi(optarg, 4, -1);
                if (INFINIT_INT == level) {
                    fprintf(stderr, "Specified wrong debug level <%s>, should be [0-3].\n",
                            optarg);
                    exit(2);
                }
            } else {

                level = gf_atoi(optarg, 6, 0);
                if (INFINIT_INT == level) {
                    fprintf(stderr, "Specified wrong timing level <%s>, should be [1-5].\n",
                            optarg);
                    exit(2);
                }
            }

            break;
        case 'o': // to turn off debugging.
            debugOptions = 1;

            break;
        case 'h':
            admin_usage(index);
            exit(0);
        case '?':
        default:
            admin_usage(index);
            exit(1);
        }
    }

    if ((index == ADMIN_JMTIME || index == ADMIN_JSTIME) && 0 == level) {
        fprintf(stderr, "Must specify -l when setting timing debug.\n");
        admin_usage(index);

        exit(1);
    }

    struct request debugRequest;
    memset(&debugRequest, 0, sizeof(struct request));

    debugRequest.keyvalues = (struct keyvalue *)calloc(5, sizeof(struct keyvalue));
    if (NULL == debugRequest.keyvalues) {
        fprintf(stderr, "calloc(%ld) failed due to %m.\n", 5*sizeof(struct keyvalue));
        exit(4);
    }

    unsigned int n=0;
    debugRequest.keyvalues[n].key = REQUEST_KEY_REQUEST_CODE;
    debugRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
    if (NULL == debugRequest.keyvalues[n].value) {
        fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
        exit(4);
    }
    snprintf(debugRequest.keyvalues[n].value, 12, "%d", debugReqCode[index]);
    n ++;

    debugRequest.keyvalues[n].key = REQUEST_KEY_OPTIONS;
    debugRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
    if (NULL == debugRequest.keyvalues[n].value) {
        fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
        exit(4);
    }
    snprintf(debugRequest.keyvalues[n].value, 12, "%d", debugOptions);
    n ++;

    debugRequest.keyvalues[n].key = REQUEST_KEY_LOGLEVEL;
    debugRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
    if (NULL == debugRequest.keyvalues[n].value) {
        fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
        exit(4);
    }
    snprintf(debugRequest.keyvalues[n].value, 12, "%d", level);
    n ++;

    if (0 < logclass) {
        debugRequest.keyvalues[n].key = REQUEST_KEY_LOGCLASS;
        debugRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
        if (NULL == debugRequest.keyvalues[n].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(debugRequest.keyvalues[n].value, 12, "%d", logclass);
        n ++;
    }

    if (0 < strlen(strFileName)) {
        debugRequest.keyvalues[n].key = REQUEST_KEY_LOGFILE;
        debugRequest.keyvalues[n].value = copy_string(strFileName);
        n ++;
    }
    debugRequest.number = n;

    int all=FALSE;
    unsigned int numNode=0;
    char **nodes=NULL;
    if (JM_DEBUG == debugReqCode[index] || JM_TIMING == debugReqCode[index]) {

        /* for JM debug:
	 *  first we query node list from JS,
	 *  then call JM one by one to set debug. */

        numNode = get_argument_names(argc, argv, optind, &nodes, &all, "hostC");

        char **nodePoint;
        if (0 == numNode && !all) {
            fprintf(stderr, "Must specify node name when setting debug to JM.\n");
            admin_usage(index);

            exit(1);
        }

        if (all) {
            nodePoint = NULL;
        } else if (0 < numNode) {
            nodePoint = nodes;
        }

        struct request nodeRequest;
        memset(&nodeRequest, 0, sizeof(struct request));

        nodeRequest.number = 3;
        nodeRequest.keyvalues = (struct keyvalue *)calloc(3, sizeof(struct keyvalue));
        if (NULL == nodeRequest.keyvalues) {
            fprintf(stderr, "calloc(%ld) failed due to %m.\n", 3*sizeof(struct keyvalue));
            exit(4);
        }

        nodeRequest.keyvalues[0].key = REQUEST_KEY_OPTIONS;
        nodeRequest.keyvalues[0].value = (char *)calloc(12, sizeof(char));
        if (NULL == nodeRequest.keyvalues[0].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(nodeRequest.keyvalues[0].value, 12, "%d", 0);

        nodeRequest.keyvalues[1].key = REQUEST_KEY_NUMBER;
        nodeRequest.keyvalues[1].value = (char *)calloc(12, sizeof(char));
        if (NULL == nodeRequest.keyvalues[1].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(nodeRequest.keyvalues[1].value, 12, "%d", numNode);

        if (NULL == nodePoint) {
            // not specify any node name, let's set debug for myself.
            nodeRequest.keyvalues[2].key = REQUEST_KEY_NAMES;
            nodeRequest.keyvalues[2].value = copy_string(qp_getmyhostname());
        } else if (0 < numNode) {
            nodeRequest.keyvalues[2].key = REQUEST_KEY_NAMES;
            unsigned int i, size = 0;
            for (i = 0; i < numNode; i++) {
                size += strlen(nodePoint[i]) + 1;
            }

            nodeRequest.keyvalues[2].value = (char *)calloc(size, sizeof(char));
            if (NULL == nodeRequest.keyvalues[2].value) {
                fprintf(stderr, "calloc(%d) failed due to %m.\n", size);
                exit(4);
            }

            for (i = 0; i < numNode; i++) {
                if (i == 0) {
                    strcpy(nodeRequest.keyvalues[2].value, nodePoint[i]);
                } else {
                    strcat(nodeRequest.keyvalues[2].value, " ");
                    strcat(nodeRequest.keyvalues[2].value, nodePoint[i]);
                }
            }
        }

        struct QuickPoolHostReply *hostReply;
        quickpool_errno = 0;
        // call JS to query node list for setting debug.
        hostReply = client_get_hostinfo(&nodeRequest, NULL);
        if (quickpool_errno != 0 || hostReply == NULL) {

            if (hostReply == NULL) {
                fprintf(stderr, "Fail to debug host due to above error.\n");
            } else {
                fprintf(stderr, "%s\n", hostReply->error_message);
            }

            gf_xdr_free(gf_xdr_request, &debugRequest);
            gf_xdr_free(gf_xdr_request, &nodeRequest);
            FREEUP(nodes);
            fflush(stderr);

            exit(2);
        }

        gf_xdr_free(gf_xdr_request, &nodeRequest);

        int ret = 0;

        // got host reply from JS, call JM one by one to set debug.
        struct QuickPoolHostInfo *hostInfo = hostReply->hosts;
        unsigned int i;
        for (i = 0; i < hostReply->num_hosts; i++) {
            if (strcmp(hostInfo[i].host, "lost_and_found") == 0 ) {
                if (!all) {
                    fprintf(stderr, "<lost_and_found> is not a real compute node, ignored.\n");
                }

                continue;
            }

            if (hostInfo[i].status & NODE_STATUS_UNAVAIL) {
                fprintf(stderr, "JM daemon is unavailable now on node <%s>.\n",
                        hostInfo[i].host);
                continue;
            }

            if (client_request_debug(&debugRequest, hostInfo[i].host) < 0) {

                log_message(ERR, NOCLASS, "Operation denied by JM on node <%s>.", hostInfo[i].host);
                gf_xdr_free(gf_xdr_request, &debugRequest);
                ret = 2;
            }

            if (debugOptions) {
                fprintf(stdout, "Unset debugging jm on node <%s>.\n", hostInfo[i].host);
            } else {
                fprintf(stdout, "Debugging jm on node <%s> started.\n", hostInfo[i].host);
            }
        }
        gf_xdr_free(gf_xdr_request, &debugRequest);
        gf_xdr_free(libxdr_host_reply, hostReply);
        FREEUP(hostReply);
        FREEUP(nodes);

        exit(ret);
    } else {

        numNode = get_argument_names(argc, argv, optind, &nodes, &all, "hostC");
        if (0 < numNode) {
            fprintf(stderr, "Node name does not need to be specified, set debug to the node which runs JS.\n");
            FREEUP(nodes);
        }

        // call JS to set debug.
        if (client_request_debug(&debugRequest, NULL) < 0) {

            log_message(ERR, NOCLASS, "Operation denied by JS.");
            gf_xdr_free(gf_xdr_request, &debugRequest);
            exit(2);
        }

        if (debugOptions) {
            fprintf(stdout, "Unset debugging js.\n");
        } else {
            fprintf(stdout, "Debugging js started.\n");
        }
        gf_xdr_free(gf_xdr_request, &debugRequest);
    }

    exit(0);
} // end function admin_debug

/**
 * @brief       To set user euid in child.
 *
 * @retval      0             #1: succeed
 * @retval      -1            #2: seteuid failed
 *
 */
static int
change_user_euid(void)
{
    uid_t uid;

    uid = getuid();

    if (uid == 0) {
        return 0;
    }

    if (seteuid(uid) < 0) {
        perror("seteuid");
        return -1;
    }

    return 0;
} // end fucntion change_user_euid

/**
 * @brief       Fork child to issue js -C.
 *
 * @param[in]   verbose       #1: indicate to print more detail info or not
 *
 * @retval      0             #1: succeed
 * @retval      -1            #2: fatal error
 * @retval      -2            #3: warning
 *
 */
static int
check_js_config(int verbose)
{
    unsigned int len;
    len = strlen(baseParams[BASE_SERVERDIR].string_value)+15;

    char *daemon;
    daemon = (char *)calloc(len+1, sizeof(char));
    if (NULL == daemon) {
        perror("calloc");
        return (-1);
    }

    // get the full name of JS.
    snprintf(daemon, len, "%s/js", baseParams[BASE_SERVERDIR].string_value);

    if (access(daemon, X_OK) < 0) {
        perror("access");
        FREEUP(daemon);
        return (-1);
    }

    char confCheckBuf[] = "RECONFIG_CHECK=TRUE";
    if (putenv(confCheckBuf)) { // tell JS that I'm going to check configuration.
        fprintf(stderr, "Failed to set environment variable RECONFIG_CHECK\n");
        FREEUP(daemon);
        return(-1);
    }

    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork");
        FREEUP(daemon);
        return (-1);
    }

    if (pid == 0) { // child, change euid and issue js -C.

        if (change_user_euid() < 0) {
            exit(-1);
        }

        execlp(daemon, daemon, "-C", (char *)0);
        perror("execlp");

        exit(-1);
    }

    FREEUP(daemon);

    if (verbose) {
        fprintf(stdout, "\nChecking configuration files for JS daemon...\n");
    }

    STATUS_WAIT_T status;
    if (waitpid(pid, &status, 0) < 0) { // wait for child done.
        perror("waitpid");
        return (-1);
    }

    if (WIFEXITED(status) != 0 && WEXITSTATUS(status) != 0xf8) {
        if (verbose)
            fprintf(stderr, "---------------------------------------------------------\n");
    }

    if (WIFEXITED(status) == 0) { // child is killed by signal.
        fprintf(stderr, "Child process killed by signal.\n\n");
        return (-1);
    }
    
    switch (WEXITSTATUS(status)) {
    case 0: // configuration is ok.

        fprintf(stderr, "No configuration errors.\n\n");
        return(0);
    case 0xff: // fatal error in configuration.

        fprintf(stderr, "There are fatal errors.\n\n");
        return (-1);
    case 0xf8: // failed to run js -C.

        fprintf(stderr, "Fail to run checking program \n\n");
        return (-1);
    case 0xfe: // warning in configuration.

        fprintf(stderr, "\nWarning: Some configuration parameters may be incorrect.\n");
        fprintf(stderr, "         They are either ignored or replaced by default values.\n");

        return (-2);
    default:

        fprintf(stderr, "Unknown errors found.\n");
        return (-1);
    }
} // end function check_js_config

/**
 * @brief       Fork child to issue jm -C.
 *
 * @param[in]   verbose       #1: indicate to print more detail info or not
 *
 * @retval      0             #1: succeed
 * @retval      -1            #2: fatal error
 * @retval      -2            #3: warning
 *
 */
static int
check_jm_config(int verbose)
{
    unsigned int len;
    len = strlen(baseParams[BASE_SERVERDIR].string_value)+15;

    char *daemon;
    daemon = (char *)calloc(len+1, sizeof(char));
    if (NULL == daemon) {
        perror("calloc");
        return (-1);
    }

    // get the full name of JM.
    snprintf(daemon, len, "%s/jm", baseParams[BASE_SERVERDIR].string_value);

    if (access(daemon, X_OK) < 0) {
        perror("access");
        FREEUP(daemon);
        return (-1);
    }

    char confCheckBuf[] = "RECONFIG_CHECK=TRUE";
    if (putenv(confCheckBuf)) { // tell JM that I'm going to check configuration.
        fprintf(stderr, "Failed to set environment variable RECONFIG_CHECK\n");
        FREEUP(daemon);
        return(-1);
    }

    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork");
        FREEUP(daemon);
        return (-1);
    }

    if (pid == 0) { // child, change euid and issue jm -C.

        if (change_user_euid() < 0) {
            fprintf(stderr, "change_user_euid error line\n");
            exit(-1);
        }

        execlp(daemon, daemon, "-C", (char *)0);
        perror("execlp");
        exit(-1);
    }

    FREEUP(daemon);

    if (verbose) {
        fprintf(stdout, "\nChecking configuration files for JM daemon...\n");
    }

    STATUS_WAIT_T status;
    if (waitpid(pid, &status, 0) < 0) { // wait for child done.
        perror("waitpid");
        return (-1);
    }

    if (WIFEXITED(status) != 0 && WEXITSTATUS(status) != 0xf8) {
        if (verbose)
            fprintf(stderr, "---------------------------------------------------------\n");
    }

    if (WIFEXITED(status) == 0) { // child is killed by signal.
        fprintf(stderr, "Child process killed by signal.\n\n");
        return (-1);
    }

    switch (WEXITSTATUS(status)) {
    case 0: // configuration is ok.

        fprintf(stderr, "No configuration errors.\n\n");
        return(0);
    case 0xff: // fatal error in configuration.

        fprintf(stderr, "There are fatal errors.\n\n");
        return (-1);
    case 0xf8: // failed to run js -C.

        fprintf(stderr, "Fail to run checking program \n\n");
        return (-1);
    case 0xfe: // warning in configuration.

        fprintf(stderr, "\nWarning: Some configuration parameters may be incorrect.\n");
        fprintf(stderr, "         They are either ignored or replaced by default values.\n");

        return (-2);
    default:

        fprintf(stderr, "Unknown errors found.\n");
        return (-1);
    }
} // en

